Extracting Presentational Components (AddTodo, Footer, FilterLink)
We've refactored TodoApp to use a TodoList component.
Let's keep working on separating the looks from the behavior.
Current TodoApp Code
class TodoApp extends Component {render () {const {todos,visibilityFilter} = this.props;const visibleTodos = getVisibleTodos(todos,visibilityFilter);return (<div><input ref={node => {this.input = node;}} /><button onClick={() => {store.dispatch({type: 'ADD_TODO',text: this.input.value,id: nextTodoId++});this.input.value = '';}}>Add Todo</button><TodoListtodos={visibleTodos}onTodoClick={id =>store.dispatch({type: 'TOGGLE_TODO',id})} /><p>Show:{' '}<FilterLinkfilter="SHOW_ALL"currentFilter={visibilityFilter}>All</FilterLink>{', '}<FilterLinkfilter='SHOW_ACTIVE'currentFilter={visibilityFilter}>Active</FilterLink>{', '}<FilterLinkfilter='SHOW_COMPLETED'currentFilter={visibilityFilter}>Completed</FilterLink></p></div>);}}
Extracting the Input and the Button into AddTodo
We will combine the input and the button into one new component called AddTodo.
Functional components don't have instances, so instead of using this, we will use a variable called input that we will close over so we can write to it inside of the function.
Since we want AddTodo to be a presentational component, we will have the button call an onAddClick() function with input's value as its parameter. We also make onAddClick a prop so that the component that uses AddTodo can specify what happens with the "Add Todo" button is clicked.
const AddTodo = ({onAddClick}) => {let input;return (<div><input ref={node => {input = node;}} /><button onClick={() => {onAddClick(input.value);input.value = '';}}>Add Todo</button></div>);};
Now we need to update the TodoApp container component by replacing the <input> and <button> entries with our new AddTodo component.
We will also specify our onAddClick function to dispatch an action of type 'ADD_TODO' along with the corresponding text and next id.
.. // inside `TodoApp`'s `render` method.return (<div><AddTodoonAddClick={text =>store.dispatch({type: 'ADD_TODO',id: nextTodoId++,text})}/>...
Extracting the FilterLink Footer Elements
Now we will create a new functional component called Footer. Since each FilterLink needs to know the visibilityFilter, we will make that a prop.
We want the Filter and FilterLink to be presentational components, but in its current implementation each of the FilterLinks contain a store.dispatch() call. This call will be replaced by an onClick call that will take a single parameter with the filter. We also add onClick to FilterLink's props.
Since onClick is now a prop for FilterLink, we need to specify it every time that FilterLink is used in our Footer. Adding onClick={onFilterClick} makes sure that onClick makes it to FilterLink as a prop.
// FilterLink was built in a previous sectionconst FilterLink = ({filter,currentFilter,children,onClick}) => {if (filter === currentFilter) {return <span>{children}</span>}return (<a href='#'onClick={e => {e.preventDefault();onClick(filter);}}>{children}</a>);};const Footer = ({visibilityFilter,onFilterClick}) => (<p><FilterLinkfilter='SHOW_ALL'currentFilter={visibilityFilter}onClick={onFilterClick}>All</FilterLink>{', '}<FilterLinkfilter='SHOW_ACTIVE'currentFilter={visibilityFilter}onClick={onFilterClick}>Active</FilterLink>{', '}<FilterLinkfilter='SHOW_COMPLETED'currentFilter={visibilityFilter}onClick={onFilterClick}>Completed</FilterLink></p>);
Adding Footer to TodoApp
When adding the Footer component into TodoApp, we need to pass two props. First, visibilityFilter to highlight the active link. The second prop is onFilterClick, which will dispatch an action of type 'SET_VISIBILITY_FILTER' along with the filter being clicked.
.. // inside `TodoApp`'s `render` method.return (<div>// `<AddTodo>` component// `<TodoList>` component<FootervisibilityFilter={visibilityFilter}onFilterClick={filter =>store.dispatch({type: 'SET_VISIBILITY_FILTER',filter})}/></div>...
Changing TodoApp into a function
It is possible to change TodoApp from a class into a function.
This allows us to eliminate the destructuring of todos and visibilityFilter from this.props inside the render function. Instead, we can do this inside the argument to the TodoApp function.
We can also do away with the render() declaration.
Since the visibleTodos are only used in a single place, we can move its declaration into the TodoList todos prop declaration.
const TodoApp = ({todos,visibilityFilter}) => {return (<div><AddTodoonAddClick={text =>store.dispatch({type: 'ADD_TODO',id: nextTodoId++,text})}/><TodoListtodos={getVisibleTodos(todos,visibilityFilter)}onTodoClick={id =>store.dispatch({type: 'TOGGLE_TODO',id})}/><FootervisibilityFilter={visibilityFilter}onFilterClick={filter =>store.dispatch({type: 'SET_VISIBILITY_FILTER',filter})}/></div>);}
Recap of the Data Flow
We've now finished the initial refactor of our application into a single container component with many presentational components inside of it.
Separation of presentational components isn't required in Redux, but it's a good pattern to follow because it decouples our rendering from Redux. This way, if we choose to move to another framework like Relay, we can keep our presentation components as-is.
One of the downsides to having separate presentational components is that we have to move around a lot of props, including the callbacks. However, we can easily solve this problem by introducing many intermediate container components, which we will start on in the next section.